<?php

namespace Mvc;

use PDO;
use Exception;

// 模型类
class Model
{
    // 数据库操作
    public static $pdo;
    private static $db_config;

    // 配置信息
    public function config($config = [])
    {
        if (empty($config)) {
            $_config = require dirname(__DIR__) . '/config.php';
            $db_config = $_config['db'];
        } else {
            $db_config = array_merge(array(
                'type' => 'mysql',
                'host' => '127.0.0.1',
                'dbname' => 'test',
                'port' => '3306',
                'charset' => 'utf8',
                'username' => 'root',
                'password' => 'root'
            ), (array)$config);
        }
        self::$db_config = $db_config;
    }

    public function getPDO()
    {
        if (self::$db_config == null) {
            if ($config = $this->config())
                self::$db_config = $config;
        }
        if (self::$pdo == null) {
            try {
                self::$pdo = new PDO(self::$db_config['type'] . ':host=' . self::$db_config['host'] . ';dbname=' . self::$db_config['dbname'] . ';port=' . self::$db_config['port'] . ';charset=' . self::$db_config['charset'], self::$db_config['username'], self::$db_config['password']);
                self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (Exception $ex) {
                exit($ex->getMessage());
            }
        }
        return self::$pdo;
    }

    /**
     * 开始事务
     * @return bool
     */
    public function beginTransaction()
    {
        return $this->getPDO()->beginTransaction();
    }

    /**
     * 提交事务
     * @return bool
     */
    public function commit()
    {
        return $this->getPDO()->commit();
    }

    /**
     * 事务回滚
     * @return bool
     */
    public function rollBack()
    {
        return $this->getPDO()->rollBack();
    }

    /**
     * 错误代码
     * @return mixed
     */
    public function errorCode()
    {
        return $this->getPDO()->errorCode();
    }

    /**
     * 错误信息
     * @return array
     */
    public function errorInfo()
    {
        return $this->getPDO()->errorInfo();
    }

    /**
     * 上次插入的ID
     * @return string
     */
    public function lastInsertId()
    {
        return $this->getPDO()->lastInsertId();
    }

    /**
     * 获取一条记录
     * @param $sql
     * @param array $params
     * @return mixed
     */
    public function fetch($sql, $params = array(), $fetchMode = PDO::FETCH_ASSOC)
    {
        $results = $this->prepare($sql, $params);
        $results->setFetchMode($fetchMode);
        return $results->fetch();
    }

    /**
     * 获取一个字段值
     * @param $sql
     * @param array $params
     * @return string
     */
    public function fetchColumn($sql, $params = array())
    {
        $results = $this->prepare($sql, $params);
        return $results->fetchColumn();
    }

    /**
     * 获取一个数据集
     * @param $sql
     * @param array $params
     * @return array
     */
    public function fetchAll($sql, $params = array(), $keyfield = '', $fetchMode = PDO::FETCH_ASSOC)
    {
        $results = $this->prepare($sql, $params);
        $results->setFetchMode($fetchMode);
        $res = $results->fetchAll();
        $ret = $res;
        if (!empty($res) && !empty($keyfield)) {
            $ret = array();
            foreach ($res as $val) {
                $ret[$val[$keyfield]] = $val;
            }
        }
        return $ret;
    }

    public function insert($table, $data = array(), $replace = false)
    {
        $cmd = $replace ? 'REPLACE INTO' : 'INSERT INTO';
        $condition = $this->implode($data, ',');
        return $this->exec($cmd . ' ' . $table . (' SET ' . $condition['fields']), $condition['params']);
    }

    public function update($table, $data = array(), $params = array(), $glue = 'AND')
    {
        $fields = $this->implode($data, ',');
        $condition = $this->implode($params, $glue);
        $params = array_merge($fields['params'], $condition['params']);
        $sql = 'UPDATE ' . $table . (' SET ' . $fields['fields']);
        $sql .= $condition['fields'] ? ' WHERE ' . $condition['fields'] : '';
        return $this->exec($sql, $params);
    }

    public function delete($table, $params = array(), $glue = 'AND')
    {
        $condition = $this->implode($params, $glue);
        $sql = 'DELETE FROM ' . $table;
        $sql .= $condition['fields'] ? ' WHERE ' . $condition['fields'] : '';
        return $this->exec($sql, $condition['params']);
    }

    private function implode($params, $glue = ',')
    {
        $result = array(
            'fields' => ' 1 ',
            'params' => array()
        );
        $split = '';
        $suffix = '';
        $allow_operator = array('>', '<', '<>', '!=', '>=', '<=', '+=', '-=', 'LIKE', 'like');
        if (in_array(strtolower($glue), array('and', 'or'))) {
            $suffix = '__';
        }
        if (!is_array($params)) {
            $result['fields'] = $params;
            return $result;
        }
        if (is_array($params)) {
            $result['fields'] = '';
            foreach ($params as $fields => $value) {
                $operator = '';
                if (strpos($fields, ' ') !== false) {
                    list($fields, $operator) = explode(' ', $fields, 2);
                    if (!in_array($operator, $allow_operator)) {
                        $operator = '';
                    }
                }
                if (empty($operator)) {
                    $fields = trim($fields);
                    if (is_array($value) && !empty($value)) {
                        $operator = 'IN';
                    } else {
                        $operator = '=';
                    }
                } else if ($operator == '+=') {
                    $operator = ' = `' . $fields . '` + ';
                } else if ($operator == '-=') {
                    $operator = ' = `' . $fields . '` - ';
                } else {
                    if ($operator == '!=' || $operator == '<>') {
                        if (is_array($value) && !empty($value)) {
                            $operator = 'NOT IN';
                        }
                    }
                }
                if (is_array($value) && !empty($value)) {
                    $insql = array();
                    $value = array_values($value);
                    foreach ($value as $k => $v) {
                        $insql[] = ':' . $suffix . $fields . '_' . $k;
                        $result['params'][':' . $suffix . $fields . '_' . $k] = is_null($v) ? '' : $v;
                    }
                    $result['fields'] .= $split . ('`' . $fields . '` ' . $operator . ' (') . implode(',', $insql) . ')';
                    $split = ' ' . $glue . ' ';
                } else {
                    $result['fields'] .= $split . ('`' . $fields . '` ' . $operator . ' :' . $suffix . $fields);
                    $split = ' ' . $glue . ' ';
                    $result['params'][':' . $suffix . $fields] = is_null($value) || is_array($value) ? '' : $value;
                }
            }
        }
        return $result;
    }

    /**
     * 执行SQL, 插入返回 lastInsertID 其他返回影响行数
     * @param $sql
     * @param array $params
     * @return int|string
     */
    public function exec($sql, $params = array())
    {
        $results = $this->prepare($sql, $params);
        if (preg_match('/^\\s*(INSERT\\s+INTO|REPLACE\\s+INTO)\\s+/i', $sql)) {
            return (int)$this->getPDO()->lastInsertId();
        }
        return $results->rowCount();
    }

    protected function prepare($sql, $params = array())
    {
        try {
            $stmt = $this->getPDO()->prepare($sql);
            if (!is_array($params)) {
                $params = array();
            }
            $exec = $stmt->execute($params);
            if ($exec) {
                return $stmt;
            }
            return false;
        } catch (Exception $ex) {
            if ($ex->getCode() == 'HY000') {
                self::$pdo = NULL;
                return $this->prepare($sql, $params);
            }
            throw $ex;
        }
    }
}
